// src/logic/orchestrator.js
// 全局音频编排器（配置握手 + 心跳重连 + isLive + 事件优先/主泵限流/预热 + 调试日志）
// 方案A：autoConnectOnVisible / autoConnectIfEverConnected / everConnected / userInitiatedConnect
// 调试增强：统一 [Orch] 日志；关键路径详尽 console；事件兜底文案；节流重置；WS未连时事件排队不丢。
// ★ 修复点：
// - onclose/onerror 时重置 inflight/队列，避免并发残留导致主泵永久停摆
// - “主话术”增加 meta 看门狗（超时自动回滚 inflight）
// - inflightTotal 的减回仅在 main（二进制到达）时执行，避免事件音频误减

const Orchestrator = (function () {
    // ===== WS 地址 =====
    // var SYNTH_URL = "ws://192.168.1.153:8000/ws/audio";
    // var CHAT_URL  = "ws://192.168.1.153:8000/ws/chat";
    // var WX_URL    = "ws://192.168.1.153:8000/ws/wx";

    var SYNTH_URL = "wss://api.nextai.club/ws/audio";
    var CHAT_URL  = "wss://api.nextai.club/ws/chat";
    var WX_URL    = "wss://api.nextai.club/ws/wx";

    // ===== 外部注入 =====
    var getConfig = function () { return {}; };
    var getToken  = function () { return ""; };
    var onConfigUpdate = function () {};
    var onLog = function () {};

    // ★ 新增：视频号回调（UI 层可注入）
    var wxHandlers = {
        onQr: function (data) {},                           // {qrcode_base64, qr_mime?}
        onQrStatus: function (qr_status, data) {},          // 0=登录成功, 其它见后端说明
        onLoginOk: function (data) {},                      // {nickname, username, finderUsername}
        onRoomStatus: function (live_status, data) {},      // 1=开播, 2=已结束, 其它=未开播/未知
        onStatus: function (state) {},                      // "stopped" 等
        onRaw: function (msg) {},                           // 可选，收到任何 wx 消息
    };

    // ===== 方案A：自动连接策略位 =====
    var autoConnectOnVisible = false;            // 页面可见时是否自动 ensureConnected（默认 false）
    var autoConnectIfEverConnected = true;       // 仅在“手动连过或曾成功连通过”才自动连（默认 true）
    var everConnected = false;                   // 曾经至少有一个 WS 连接成功过
    var userInitiatedConnect = false;            // 用户“手动连”标志

    // ===== 调试 =====
    var DEBUG = true;                            // init({debug:false}) 可关闭
    function _cl(level, args) {
        if (!DEBUG) return;
        try { (console[level] || console.log).apply(console, ["[Orch]"].concat([].slice.call(args))); }
        catch (_) { try { console.log.apply(console, ["[Orch]"].concat([].slice.call(args))); } catch (e) {} }
    }
    function log() { try { onLog.apply(null, arguments); } catch (e) {} _cl("log", arguments); }
    function warn(){ _cl("warn", arguments); }
    function error(){ _cl("error", arguments); }

    // ===== 工具 =====
    function now() { return Date.now(); }
    function clamp(n, d, lo, hi) { var x = Number(n); if (isNaN(x)) x = d; if (lo != null) x = Math.max(lo, x); if (hi != null) x = Math.min(hi, x); return x; }
    function pick(arr, name) {
        if (!arr || !arr.length) return "";
        var text = String(arr[Math.floor(Math.random() * arr.length)] || "");
        var n = String(name || "").trim();
        if (n) n = Array.from(n).slice(0, 4).join("");
        if (text.indexOf("{user_name}") !== -1) text = text.replace(/\{user_name\}/g, n || "朋友");
        return text;
    }
    function safeCfg() { try { return Object.assign({}, getConfig() || {}); } catch (e) { return {}; } }
    function safeToken() {
        try {
            var raw = (getToken && getToken()) || ""; raw = String(raw || "").trim();
            if (!raw) return "";
            return raw.toLowerCase().startsWith("bearer ") ? raw : ("Bearer " + raw);
        } catch (e) { return ""; }
    }
    function revokeUrl(u) { try { if (u) URL.revokeObjectURL(u); } catch (e) {} }

    // ===== Live 状态（仅根据 audio/synth WS）=====
    function setLiveFlag(v) { try { if (window && window.store) window.store.isLive = !!v; } catch (e) {} }
    function refreshLiveFlag() {
        try {
            var v = !!(synthWS && (synthWS.readyState === WebSocket.OPEN || synthWS.readyState === WebSocket.CONNECTING));
            setLiveFlag(v);
        } catch (e) { setLiveFlag(false); }
    }

    // ===== 合成 WS =====
    var synthWS = null, synthOpen = false, synthClosing = false;
    var synthReady = false; // 握手成功后置 true
    var pendingSynth = [];  // 未 ready/未 open 的合成请求暂存 [{payload, cb}]

    // meta 队列（严格 FIFO 与二进制配对）
    var metaQueue = []; // [{id,qtype,mime,text,size,ts}]

    // ===== 并发与主泵节流（核心）=====
    var inflightTotal = 0;     // 仅统计“主”发出的在途
    var inflightMain  = 0;

    var MAX_MAIN = 7;                              // 主队列长度上限
    var MAIN_MAX_INFLIGHT_DEFAULT = 2;             // 主最大在途
    var MAIN_MIN_GAP_MS_DEFAULT   = 1000;          // 主两次最小间隔
    var MAIN_WARMUP_MS_DEFAULT    = 500;           // config_ok 后预热时长
    var EVENT_PAUSE_MS_DEFAULT    = 1000;          // 事件到来后暂停主泵时长

    var lastMainSynthAt   = 0;   // 最近一次主合成发起时间
    var mainWarmupUntil   = 0;   // 预热截止时间（config_ok 设置）
    var pumpPausedUntil   = 0;   // 因事件而暂停主泵的截止时间

    // ===== Synth 心跳 + 自动重连 =====
    var SYNTH_HEARTBEAT = 25000, synthHeart = null;
    function startSynthHB(){
        stopSynthHB();
        synthHeart = setInterval(function(){
            if (synthWS && synthWS.readyState === WebSocket.OPEN) {
                try { synthWS.send(JSON.stringify({ type: "ping" })); } catch (e) {}
            }
        }, SYNTH_HEARTBEAT);
    }
    function stopSynthHB(){ if (synthHeart) { clearInterval(synthHeart); synthHeart = null; } }

    var synthReconnectTimer = null, synthBackoff = 1000, synthBackoffMax = 30000;
    function scheduleSynthReconnect(){
        if (synthClosing) return;
        if (synthReconnectTimer) return;
        synthReconnectTimer = setTimeout(function(){
            synthReconnectTimer = null;
            if (safeToken()) { log("[synth] reconnect..."); connectSynth(); }
            synthBackoff = Math.min(synthBackoff * 2, synthBackoffMax);
        }, synthBackoff);
    }

    // ===== Chat WS (抖音) =====
    var chatWS = null, chatOpen = false, chatClosing = false;
    var chatHeart = null, HEARTBEAT = 25000;
    var chatOutbox = [];
    var lastChatMsg = null;

    var chatReconnectTimer = null, chatBackoff = 1000, chatBackoffMax = 30000;
    function scheduleChatReconnect(){
        if (chatClosing) return;
        if (chatReconnectTimer) return;
        chatReconnectTimer = setTimeout(function(){
            chatReconnectTimer = null;
            if (safeToken()) { log("[chat] reconnect..."); connectChat(); }
            chatBackoff = Math.min(chatBackoff * 2, chatBackoffMax);
        }, chatBackoff);
    }

    // ===== 两个全局 audio =====
    var mainAudio = null;
    var eventAudio = null;
    var willResumeMain = false;

    // ===== 队列 =====
    var mainQ  = []; // {id,qtype:'main',mime,url,text,ts}
    var eventQ = []; // {id,qtype:'welcome|like|...',mime,url,text,ts}

    // ===== 主话术补货泵 =====
    var pumpTimer = null;
    var mainIdx = 0;

    // ===== 事件限流 =====
    var lastPlayedAt   = { welcome: 0, like: 0, gift: 0, follow: 0, interrupt: 0 };
    var lastEnqueuedAt = { welcome: 0, like: 0, gift: 0, follow: 0, interrupt: 0 };

    // ---------- 调试：重置事件频控 ----------
    function resetEventThrottle() {
        for (const k of ["welcome","like","gift","follow","interrupt"]) {
            lastPlayedAt[k] = 0; lastEnqueuedAt[k] = 0;
        }
        log("[event] throttle reset");
    }

    // ---------- audio ----------
    function ensureAudios() {
        if (!mainAudio) {
            mainAudio = document.createElement("audio");
            mainAudio.setAttribute("preload", "auto");
            mainAudio.setAttribute("playsinline", "");
            mainAudio.setAttribute("webkit-playsinline", "");
            mainAudio.autoplay = true;
            mainAudio.controls = true;
            mainAudio.style.position = "fixed";
            mainAudio.style.right = "8px";
            mainAudio.style.bottom = "8px";
            mainAudio.style.zIndex = "2147483647";
            mainAudio.style.display = "none";
            document.body.appendChild(mainAudio);

            mainAudio.addEventListener("ended", function () {
                var it = mainQ.shift(); if (it) revokeUrl(it.url);
                schedulePlayNextMain();
            });
            mainAudio.addEventListener("error", function () {
                var it = mainQ.shift(); if (it) revokeUrl(it.url);
                schedulePlayNextMain();
            });
        }
        if (!eventAudio) {
            eventAudio = document.createElement("audio");
            eventAudio.setAttribute("preload", "auto");
            eventAudio.setAttribute("playsinline", "");
            eventAudio.setAttribute("webkit-playsinline", "");
            eventAudio.autoplay = true;
            eventAudio.controls = false;
            eventAudio.style.display = "none";
            document.body.appendChild(eventAudio);

            eventAudio.addEventListener("play", function () {
                var it = eventQ[0];
                if (it) lastPlayedAt[it.qtype] = now();
            });
            eventAudio.addEventListener("ended", function () {
                var it = eventQ.shift();
                if (it) { revokeUrl(it.url); }
                playEventOrResumeMain();
            });
            eventAudio.addEventListener("error", function () {
                var it = eventQ.shift();
                if (it) { revokeUrl(it.url); }
                playEventOrResumeMain();
            });
        }
    }
    function unlockAudio() {
        ensureAudios();
        var p1 = mainAudio.play(); if (p1 && p1.catch) p1.catch(function () {});
        var p2 = eventAudio.play(); if (p2 && p2.catch) p2.catch(function () {});
        try { mainAudio.pause(); } catch (e) {}
        try { eventAudio.pause(); } catch (e) {}
        log("[audio] unlocked by gesture");
    }

    // ---------- 主：随机间隔 ----------
    function schedulePlayNextMain() {
        ensureAudios();
        if (eventQ.length) { pauseMain(); playEventOrResumeMain(); return; }
        if (!mainQ.length) { startPump(); return; }
        var cfg = safeCfg();
        var s = Array.isArray(cfg.mainTxtSpace) ? cfg.mainTxtSpace : [0.2, 0.3];
        var dmin = clamp(s[0], 0.2, 0, 60), dmax = clamp(s[1], 0.3, dmin, 120);
        var delay = (dmin + Math.random() * (dmax - dmin)) * 1000;
        setTimeout(function () {
            if (eventQ.length) { pauseMain(); playEventOrResumeMain(); return; }
            playMainTop();
        }, delay);
    }
    function playMainTop() {
        if (!mainQ.length) { startPump(); return; }
        if (eventQ.length || (eventAudio && !eventAudio.paused)) { pauseMain(); return; }
        var it = mainQ[0]; if (!it || !it.url) { mainQ.shift(); schedulePlayNextMain(); return; }
        mainAudio.src = it.url;
        var p = mainAudio.play(); if (p && p.catch) p.catch(function () {});
        log("[main] play:", it.text);
    }
    function pauseMain() { if (mainAudio && !mainAudio.paused) { willResumeMain = true; try { mainAudio.pause(); } catch (e) {} log("[main] paused"); } }
    function resumeMainIfNeeded() { if (willResumeMain) { willResumeMain = false; var p = mainAudio.play(); if (p && p.catch) p.catch(function () {}); log("[main] resume"); } }

    // ---------- 事件播放 ----------
    function playEventOrResumeMain() {
        if (eventAudio && !eventAudio.paused) return;
        if (eventQ.length) {
            var it = eventQ[0]; if (!it || !it.url) { eventQ.shift(); playEventOrResumeMain(); return; }
            eventAudio.src = it.url;
            var p = eventAudio.play(); if (p && p.catch) p.catch(function () {});
            log("[event] play:", it.qtype, it.text);
            return;
        }
        resumeMainIfNeeded();
        if (mainAudio.paused && mainQ.length) schedulePlayNextMain();
    }

    // ---------- ★ 新增：主话术重写（Zhipu） ----------
    function zhipuRewriteIfNeeded(rawText) {
        var cfg = safeCfg();
        var fn = (window && window.store && typeof window.store.zhipu === "function") ? window.store.zhipu : null;
        if (!cfg.mainReload || !fn) return Promise.resolve(String(rawText || ""));
        var tmo = Number(cfg.zpTimeoutMs || cfg.zhipuTimeoutMs || 4000);
        tmo = clamp(tmo, 4000, 300, 20000);
        var orig = String(rawText || "");

        return new Promise(function (resolve) {
            var done = false;
            var timer = setTimeout(function () {
                if (done) return; done = true;
                warn("[zhipu] timeout, fallback to original");
                resolve(orig);
            }, tmo);

            Promise.resolve()
                .then(function () { return fn(orig); })
                .then(function (res) {
                    if (done) return;
                    clearTimeout(timer);
                    var s = "";
                    try {
                        if (typeof res === "string") s = res;
                        else if (res && typeof res === "object") s = res.response || res.text || res.data || "";
                    } catch (e) {}
                    s = String(s || "").trim();
                    if (!s) s = orig;
                    resolve(s);
                })
                .catch(function (err) {
                    if (done) return;
                    clearTimeout(timer);
                    warn("[zhipu] error:", err);
                    resolve(orig);
                });
        });
    }

    // ---------- ★ 新增：主话术 meta 看门狗 ----------
    var metaGuards = []; // [{qtype:'main', cleared:false, timer, ts}]
    function _drainGuards() {
        for (var i = 0; i < metaGuards.length; i++) {
            var g = metaGuards[i];
            try { clearTimeout(g.timer); } catch(_) {}
        }
        metaGuards.length = 0;
    }
    function _addMainGuard(timeoutMs) {
        var guard = { qtype: "main", cleared: false, ts: now(), timer: null };
        guard.timer = setTimeout(function(){
            if (!guard.cleared) {
                // 主话术 meta/二进制未到 → 回滚并发
                if (inflightTotal > 0) inflightTotal -= 1;
                if (inflightMain  > 0) inflightMain  -= 1;
                warn("[guard] main meta timeout -> rollback inflight");
                guard.cleared = true;
            }
        }, timeoutMs);
        metaGuards.push(guard);
        return guard;
    }
    function _clearOneGuardForQtype(qtype) {
        for (var i = 0; i < metaGuards.length; i++) {
            var g = metaGuards[i];
            if (!g.cleared && g.qtype === qtype) {
                g.cleared = true;
                try { clearTimeout(g.timer); } catch(_) {}
                metaGuards.splice(i, 1);
                return true;
            }
        }
        return false;
    }

    // ---------- 主泵（限并发 + 最小间隔 + 预热 + 事件暂停） ----------
    function startPump() {
        if (pumpTimer) return;
        pumpTimer = setInterval(function () {
            var cfg = safeCfg();
            if (!cfg.enableMain) return;
            if (!synthReady) return;

            if (now() < mainWarmupUntil) return;
            if (now() < pumpPausedUntil) return;

            while (metaQueue.length && (now() - metaQueue[0].ts > 15000)) {
                var stale = metaQueue.shift();
                // 仅当丢的是 main 才回滚主并发
                if (stale.qtype === "main") {
                    if (inflightTotal > 0) inflightTotal -= 1;
                    if (inflightMain > 0)  inflightMain  -= 1;
                }
                warn("[pump] drop stale meta:", stale.qtype, stale.id);
            }

            var maxMainQueue = clamp(cfg.maxMainQueue, MAX_MAIN, 1, 20);
            var capacityLeft = maxMainQueue - (mainQ.length + inflightMain);
            if (capacityLeft <= 0) return;

            var mainMaxInflight = clamp(cfg.mainMaxInflight, MAIN_MAX_INFLIGHT_DEFAULT, 1, 6);
            if (inflightMain >= mainMaxInflight) return;

            var mainMinGap = clamp(cfg.mainSynthMinGapMs, MAIN_MIN_GAP_MS_DEFAULT, 100, 60000);
            if (now() - lastMainSynthAt < mainMinGap) return;

            var src = Array.isArray(cfg.mainTxt) ? cfg.mainTxt : [];
            if (!src.length) return;

            var originalText = String(src[mainIdx % src.length] || ""); mainIdx++;
            if (!originalText) return;

            // === 并发计数先自增（主泵） ===
            inflightTotal += 1;
            inflightMain  += 1;
            lastMainSynthAt = now();

            // === ★ 看门狗：在 main synth 发出后等待 meta，超时回滚 ===
            var guardMs = clamp(Number(cfg.metaWatchdogMs || 15000), 3000, 500, 60000);
            var guard = _addMainGuard(guardMs);

            // === Zhipu 重写 → 合成 ===
            zhipuRewriteIfNeeded(originalText)
                .then(function (finalText) {
                    if (finalText !== originalText) {
                        log("[pump] main rewritten by zhipu:", { from: originalText, to: finalText });
                    } else {
                        log("[pump] synth main:", finalText);
                    }
                    synth(finalText, "main");
                    if (mainAudio && mainAudio.paused && !eventQ.length && mainQ.length) playMainTop();
                })
                .catch(function (err) {
                    warn("[pump] zhipu rewrite failed, fallback:", err);
                    synth(originalText, "main");
                    if (mainAudio && mainAudio.paused && !eventQ.length && mainQ.length) playMainTop();
                });
        }, 300);
        log("[pump] started");
    }
    function stopPump() { if (pumpTimer) { clearInterval(pumpTimer); pumpTimer = null; log("[pump] stopped"); } }
    function fillMain() { startPump(); }

    // ---------- 弹幕事件 ----------
    function onDanmuEvent(e) {
        var cfg = safeCfg();
        var et = String((e.type || e.method || "")).toLowerCase();
        var name = String(e.user_name || "").trim();
        var qtype = null, text = null, gap = 0, enable = true;

        try { window.store.liveData.danmu.push(e); } catch (_) {}

        log("[danmu] <-", { et, name, raw:e });

        if (et === "welcome" || et === "member_enter" || et === "webcastmembermessage") {
            qtype = "welcome";
            gap = clamp(cfg.welcomeSpace, 30, 0, 600) * 1000;
            text = pick(cfg.welcometxt, name) || (name ? `${name}来了，欢迎你呀` : `欢迎来到直播间`);
            enable = (cfg.enableWelcome !== false);
        } else if (et === "like" || et === "webcastlikemessage") {
            qtype = "like";
            gap = clamp(cfg.likeTxtSpace, 60, 0, 600) * 1000;
            text = pick(cfg.likeTxt, name) || (name ? `感谢${name}的点赞` : `谢谢点赞`);
            enable = (cfg.enableLike !== false);
        } else if (et === "gift" || et === "webcastgiftmessage") {
            qtype = "gift";
            gap = clamp(cfg.giftTxtSpace, 30, 0, 600) * 1000;
            text = pick(cfg.giftTxt, name) || (name ? `感谢${name}的礼物` : `感谢礼物支持`);
            enable = (cfg.enableGift !== false);
        } else if (et === "follow" || et === "webcastsocialmessage") {
            qtype = "follow";
            gap = clamp(cfg.loveTxtSpace, 15, 0, 600) * 1000;
            text = pick(cfg.loveTxt, name) || (name ? `感谢${name}关注` : `谢谢关注`);
            enable = (cfg.enableFollow !== false);
        } else if (et === "chat" || et === "webcastchatmessage") {
            qtype = "interrupt";
            gap = clamp(cfg.dsSpace, 2, 0, 600) * 1000;
            enable = (cfg.enableSmartReply !== false);

            var orig = String(e.content || "").trim();
            if (!enable || !orig) {
                warn("[danmu] chat ignored: enable=", enable, "orig=", orig);
                return;
            }

            var lastRef = Math.max(Number(lastPlayedAt[qtype] || 0), Number(lastEnqueuedAt[qtype] || 0));
            if (now() - lastRef < gap) {
                warn("[danmu] chat throttled:", { since: now()-lastRef, gap });
                return;
            }
            lastEnqueuedAt[qtype] = now();

            var eventPauseMs = clamp(cfg.eventPauseMs, EVENT_PAUSE_MS_DEFAULT, 0, 10000);
            pumpPausedUntil = Math.max(pumpPausedUntil, now() + eventPauseMs);

            var uname = String(e.user_name || "").trim();
            var fn = (window.store && typeof window.store.deepseek === "function") ? window.store.deepseek : null;

            if (!fn) {
                var fallback = uname ? (uname + "说：" + orig) : orig;
                log("[danmu] chat fallback synth:", fallback);
                synth(fallback, qtype, function (item) {
                    eventQ.push(item);
                    if (!eventAudio || eventAudio.paused) { pauseMain(); playEventOrResumeMain(); }
                });
                return;
            }

            fn(orig, uname).then(function (res) {
                var reply = res && (res.response || res.data || res.text) ? (res.response || res.data || res.text) : "";
                reply = String(reply || "").trim();
                log("[danmu] chat ai reply:", reply);
                if (!reply) return;

                synth(reply, qtype, function (item) {
                    eventQ.push(item);
                    if (!eventAudio || eventAudio.paused) { pauseMain(); playEventOrResumeMain(); }
                });
            }).catch(function (err) {
                error("[deepseek] error:", err);
            });
            return;
        } else if (et === "stats") {
            try { window.store.liveData.total = e.total; window.store.liveData.current = e.current; } catch (_) {}
            return;
        }

        if (!qtype) { warn("[danmu] unknown type:", et); return; }
        if (!enable) { warn("[danmu] disabled:", qtype); return; }
        if (!text) { warn("[danmu] empty text:", qtype); return; }

        var lastRef2 = Math.max(Number(lastPlayedAt[qtype] || 0), Number(lastEnqueuedAt[qtype] || 0));
        if (now() - lastRef2 < gap) {
            warn("[danmu] throttled:", qtype, { since: now()-lastRef2, gap });
            return;
        }
        lastEnqueuedAt[qtype] = now();

        var eventPauseMs2 = clamp(cfg.eventPauseMs, EVENT_PAUSE_MS_DEFAULT, 0, 10000);
        pumpPausedUntil = Math.max(pumpPausedUntil, now() + eventPauseMs2);

        log("[danmu] -> synth:", { qtype, text });
        synth(text, qtype, function (item) {
            eventQ.push(item);
            if (!eventAudio || eventAudio.paused) {
                pauseMain();
                playEventOrResumeMain();
            }
        });
    }

    // ---------- 根据 store 组装并发送 config ----------
    function sendConfigFromStore(extraForm) {
        if (!synthWS || synthWS.readyState !== WebSocket.OPEN) return false;

        var st = (window && window.store) ? window.store : {};
        var tts_url = (st && st.runningData && st.runningData.ip) ? String(st.runningData.ip).trim() : "";
        if (tts_url) {
            if (!/^https?:\/\//i.test(tts_url)) tts_url = "http://" + tts_url;
            try {
                const u = new URL(tts_url);
                if (!u.port) u.port = "1352";
                if (!u.pathname || u.pathname === "/") u.pathname = "/basic-tts";
                tts_url = u.href;
            } catch (e) {}
        }

        var a = (st && st.muc) ? String(st.muc) : "";
        var ref_audio_path = a;

        var ref_text = "";
        try {
            var start = a.lastIndexOf('/');
            var end   = a.indexOf('.', (start >= 0 ? start : 0));
            ref_text  = a.slice(start + 1, (end >= 0 ? end : undefined));
        } catch (e) { ref_text = ""; }

        var form = Object.assign({
            ref_audio_path: ref_audio_path,
            ref_text: ref_text
        }, extraForm || {});

        var payload = { type: "config", preset: "f5_local", tts_url: tts_url, form: form };

        try {
            synthWS.send(JSON.stringify(payload));
            log("[synth] send config:", payload);
            return true;
        } catch (e) {
            error("[synth] send config error:", e);
            return false;
        }
    }

    function flushPendingSynth() {
        if (!synthReady || !synthWS || synthWS.readyState !== WebSocket.OPEN) return;
        var arr = pendingSynth.slice(); pendingSynth.length = 0;
        log("[synth] flush pending:", arr.length);
        arr.forEach(function (it) {
            try {
                synthWS.send(JSON.stringify(it.payload));
                if (typeof it.cb === "function") {
                    var text = it.payload.text;
                    var qtype = it.payload.qtype;
                    var start = now(); var timer = setInterval(function () {
                        if (now() - start > 10000) { clearInterval(timer); return; }
                        var list = (qtype === "main") ? mainQ : eventQ; if (!list.length) return;
                        var last = list[list.length - 1];
                        if (last && last.text === text) { clearInterval(timer); try { it.cb(last); } catch (e) {} }
                    }, 60);
                }
            } catch (e) { error("[synth] flush pending error:", e); }
        });
    }

    // ---------- 合成 WS ----------
    function ensureSynth() { if (!synthWS || synthWS.readyState !== WebSocket.OPEN) connectSynth(); }
    function connectSynth() {
        var token = safeToken(); if (!token) { log("[synth] wait login"); return; }
        if (synthWS && (synthWS.readyState === WebSocket.OPEN || synthWS.readyState === WebSocket.CONNECTING)) return;

        synthClosing = false;
        synthReady = false;
        log("[synth] connecting:", SYNTH_URL);
        synthWS = new WebSocket(SYNTH_URL + "?token=" + encodeURIComponent(token));
        synthWS.binaryType = "arraybuffer";
        refreshLiveFlag();

        synthWS.onopen = function () {
            synthOpen = true;
            everConnected = true;
            startSynthHB();
            synthBackoff = 1000;
            log("[synth] open");
            refreshLiveFlag();
            sendConfigFromStore(); // onopen 发送 config
        };
        function _resetInflight(reason) {
            // 清守卫 + 队列 + 并发计数 + 预热/暂停窗口
            _drainGuards();
            metaQueue.length = 0;
            inflightTotal = 0;
            inflightMain  = 0;
            lastMainSynthAt = 0;
            pumpPausedUntil = 0;
            log("[synth] inflight reset:", reason);
        }
        synthWS.onclose = function (ev) {
            synthOpen = false;
            stopSynthHB();
            synthWS = null;
            setLiveFlag(false);
            userInitiatedConnect = false; // ★ 断开复位“手动连”
            _resetInflight("ws close");   // ★ 修复：断线时回滚
            warn("[synth] closed:", ev && ev.code, ev && ev.reason);
            if (!synthClosing) scheduleSynthReconnect();
        };
        synthWS.onerror = function (e) {
            warn("[synth] error:", e && (e.message || e));
            refreshLiveFlag();
            _resetInflight("ws error");    // ★ 修复：异常时回滚
            if (!synthClosing) scheduleSynthReconnect();
        };

        synthWS.onmessage = function (ev) {
            if (typeof ev.data === "string") {
                var msg; try { msg = JSON.parse(ev.data); } catch (_) { return; }
                var t = String(msg.type || "").toLowerCase();
                if (t !== "ping") log("[synth] <-", msg);

                if (t === "hello") {
                    if (msg.need_config && !synthReady) sendConfigFromStore();
                    return;
                }
                if (t === "config_ok") {
                    synthReady = true;
                    var cfg = safeCfg();
                    var warm = clamp(cfg.mainWarmupMs, MAIN_WARMUP_MS_DEFAULT, 0, 60000);
                    mainWarmupUntil = now() + warm;
                    log("[synth] config_ok, ready=true, warmup(ms)=", warm);
                    flushPendingSynth();
                    return;
                }
                if (t === "config_error") { synthReady = false; error("[synth] config_error:", msg); return; }
                if (t === "config_snapshot") { log("[synth] config_snapshot:", msg); return; }

                if (t === "audio_meta" || t === "main_meta" || t === "event_meta") {
                    // ★ 收到 meta → 先清守卫（按 qtype）
                    var qt = msg.qtype || (t === "main_meta" ? "main" : (t === "event_meta" ? "event" : "event"));
                    _clearOneGuardForQtype(qt);

                    metaQueue.push({
                        id: msg.id,
                        qtype: qt,
                        mime: msg.mime || "audio/wav",
                        text: msg.text || "",
                        size: Number(msg.size || 0),
                        ts: now()
                    });
                    log("[synth] meta queued:", qt, msg.id, "metaQ=", metaQueue.length);
                    return;
                }

                if (t === "gc" || t === "uc") {
                    try { onConfigUpdate(msg.data); } catch (e) {}
                }
                return;
            }

            if (!metaQueue.length) { warn("[synth] bin w/o meta, drop"); return; }
            var meta = metaQueue.shift();

            // ★ 二进制到达 → 只有 main 才回滚主并发
            if (meta.qtype === "main") {
                if (inflightTotal > 0) inflightTotal -= 1;
                if (inflightMain  > 0) inflightMain  -= 1;
            }

            var blob = new Blob([ev.data], { type: meta.mime || "audio/wav" });
            var url = URL.createObjectURL(blob);
            var item = { id: meta.id, qtype: meta.qtype, mime: meta.mime, text: meta.text, url: url, ts: now() };

            if (item.qtype !== "main") {
                eventQ.push(item);
                log("[synth] bin->event push:", item.qtype, item.text, "eventQ=", eventQ.length);
                if (!eventAudio || eventAudio.paused) { pauseMain(); playEventOrResumeMain(); }
            } else {
                mainQ.push(item);
                log("[synth] bin->main push:", item.text, "mainQ=", mainQ.length);
                if (mainAudio && mainAudio.paused && !eventQ.length) playMainTop();
            }
        };
    }
    function disconnectSynth() {
        synthClosing = true;
        stopSynthHB();
        if (synthWS) { try { synthWS.close(1000, "normal"); } catch (e) {} }
        synthWS = null; synthOpen = false; synthReady = false;
        pendingSynth.length = 0;
        setLiveFlag(false);
        userInitiatedConnect = false; // ★ 断开复位“手动连”
        log("[synth] disconnect() called");
    }

    function synth(text, qtype, cb) {
        ensureSynth();
        var payload = { type: "synthesize", qtype: String(qtype || "main"), text: String(text || "") };

        // WS 未 open：不丢，入 pending，待 ready 后 flush
        if (!synthWS || synthWS.readyState !== WebSocket.OPEN) {
            pendingSynth.push({ payload, cb });
            if (pendingSynth.length > 200) pendingSynth.shift();
            warn("[synth] not open, queued:", payload, "pending=", pendingSynth.length);
            connectSynth();
            return;
        }

        // 未 ready：事件允许排队，主在未 ready 时忽略（保持原策略）
        if (!synthReady) {
            if (payload.qtype !== "main") {
                pendingSynth.push({ payload, cb });
                log("[synth] queued (not ready):", payload, "pending=", pendingSynth.length);
            } else {
                warn("[synth] ignore main before ready");
            }
            return;
        }

        try { synthWS.send(JSON.stringify(payload)); log("[synth] ->", payload); } catch (e) { error("[synth] send error:", e); return; }

        if (typeof cb === "function") {
            var start = now(); var timer = setInterval(function () {
                if (now() - start > 10000) { clearInterval(timer); return; }
                var arr = (payload.qtype === "main") ? mainQ : eventQ; if (!arr.length) return;
                var it = arr[arr.length - 1]; if (it && it.text === payload.text) { clearInterval(timer); try { cb(it); } catch (e) {} }
            }, 60);
        }
    }

    // ---------- Chat WS ----------
    function connectChat() {
        var token = safeToken(); if (!token) { log("[chat] wait login"); return; }
        if (chatWS && (chatWS.readyState === WebSocket.OPEN || chatWS.readyState === WebSocket.CONNECTING)) return;

        chatClosing = false;
        log("[chat] connecting:", CHAT_URL);
        chatWS = new WebSocket(CHAT_URL + "?token=" + encodeURIComponent(token));

        chatWS.onopen  = function () {
            chatOpen = true;
            everConnected = true;
            startHB();
            chatBackoff = 1000;
            flushOutbox();
            // ★ 新增：在 chat WS 连接成功后自动发送 sendDanmu(store.userConfig.dyName)
            var dyName = (window && window.store && window.store.userConfig ? window.store.userConfig.dyName : "");
            if (dyName) {
                sendDanmu(dyName);
                log("[chat] auto sendDanmu:", dyName);
            }
            log("[chat] open");
        };
        chatWS.onclose = function (ev) {
            chatOpen = false;
            stopHB();
            chatWS = null;
            userInitiatedConnect = false; // ★ 断开复位“手动连”
            warn("[chat] closed:", ev && ev.code, ev && ev.reason);
            if (!chatClosing) scheduleChatReconnect();
        };
        chatWS.onerror = function (e) {
            warn("[chat] error:", e && (e.message || e));
            if (!chatClosing) scheduleChatReconnect();
        };

        chatWS.onmessage = function (ev) {
            var msg; try { msg = JSON.parse(ev.data); } catch (_) { return; }
            lastChatMsg = msg;

            if (msg.type !== "ping") log("[chat] <-", msg);

            if (msg.type === "danmu_event" && msg.data) {
                log("[chat] danmu_event data:", msg.data);
                try { onDanmuEvent(msg.data); } catch (e) { error("[danmu] handler error:", e); }
            } else if (msg.type === "gc" || msg.type === "uc") {
                try { onConfigUpdate(msg.data); } catch (e) {}
            }
        };
    }
    function disconnectChat() {
        chatClosing = true;
        stopHB();
        if (chatWS) { try { chatWS.close(1000, "normal"); } catch (e) {} }
        chatWS = null; chatOpen = false;
        userInitiatedConnect = false; // ★ 断开复位“手动连”
        log("[chat] disconnect() called");
    }
    function startHB() { stopHB(); chatHeart = setInterval(function () { if (chatWS && chatWS.readyState === WebSocket.OPEN) { try { chatWS.send(JSON.stringify({ type: "ping" })); } catch (e) {} } }, HEARTBEAT); }
    function stopHB() { if (chatHeart) { clearInterval(chatHeart); chatHeart = null; } }
    function sendChat(obj) {
        if (chatWS && chatWS.readyState === WebSocket.OPEN) { try { chatWS.send(JSON.stringify(obj)); return true; } catch (e) { return false; } }
        chatOutbox.push(obj); connectChat(); return true;
    }
    function flushOutbox() { if (!chatWS || chatWS.readyState !== WebSocket.OPEN) return; var arr = chatOutbox.slice(); chatOutbox.length = 0; arr.forEach(function (o) { try { chatWS.send(JSON.stringify(o)); } catch (e) {} }); }
    function sendDanmu(id) { return sendChat({ type: "danmu", data: { id: String(id || "") } }); }
    function sendGetUserInfo() { return sendChat({ type: "gu" }); }
    function sendGetUserConfig() { return sendChat({ type: "gc" }); }
    function sendSetUserConfig(data) { return sendChat({ type: "uc", data: data || {} }); }

    // ===== 立即刷新配置（外部可调用）=====
    function forceRefreshConfig(opts) {
        opts = opts || {};
        var pushTTS    = (opts.pushTTS !== false);
        var pushChat   = !!opts.pushChat;
        var dropQueued = !!opts.dropQueued;
        var extraForm  = opts.extraForm || null;

        ensureConnected();

        if (dropQueued) {
            try { if (mainAudio) mainAudio.pause(); } catch (e) {}
            try { if (eventAudio) eventAudio.pause(); } catch (e) {}
            mainQ.forEach(function (it){ revokeUrl(it.url); });
            eventQ.forEach(function (it){ revokeUrl(it.url); });
            mainQ.length = 0; eventQ.length = 0;
            metaQueue.length = 0;
            inflightTotal = 0; inflightMain = 0;
            resetEventThrottle(); // ★ 清队列时重置节流
            log("[force] queues cleared");
        }

        if (pushTTS) {
            if (synthWS && synthWS.readyState === WebSocket.OPEN) sendConfigFromStore(extraForm);
            else connectSynth();
        }

        if (pushChat) {
            try {
                var cfg = safeCfg();
                sendSetUserConfig(cfg);
                log("[force] push cfg to chat");
            } catch (e) {}
        }

        lastMainSynthAt = 0;
        pumpPausedUntil = 0;
        mainWarmupUntil = now(); // 取消预热等待
        stopPump(); startPump();

        if (mainAudio && mainAudio.paused && !eventQ.length && mainQ.length) schedulePlayNextMain();
    }

    // ---------- 登录/登出 ----------
    function clearQueues() {
        try { if (mainAudio) mainAudio.pause(); } catch (e) {}
        try { if (eventAudio) eventAudio.pause(); } catch (e) {}

        mainQ.forEach(function (it){ revokeUrl(it.url); });
        eventQ.forEach(function (it){ revokeUrl(it.url); });
        mainQ.length = 0;
        eventQ.length = 0;

        willResumeMain = false;
        metaQueue.length = 0;
        inflightTotal = 0;
        inflightMain  = 0;
        mainIdx = 0;

        lastMainSynthAt = 0;
        mainWarmupUntil = 0;
        pumpPausedUntil = 0;

        resetEventThrottle();
        _drainGuards();
        log("[login] queues cleared");
    }
    function ensureConnected() {
        if (!safeToken()) { warn("[ensure] no token, skip"); return; }
        connectSynth();
        connectChat();
        fillMain();
    }
    function onLogin() {
        userInitiatedConnect = true;
        ensureAudios();
        ensureConnected();
        log("[login] onLogin -> userInitiatedConnect=true");
    }
    function onLoginWx() {
        userInitiatedConnect = true;
        ensureAudios();
        connectSynth();
        fillMain();
        log("[login] onLoginWx -> userInitiatedConnect=true");
    }
    function onLogout() {
        stopHB();
        disconnectChat();
        disconnectSynth();
        // ★ 新增：登出时也断开 wx
        disconnectWx();
        stopPump();
        clearQueues();
        userInitiatedConnect = false;
        log("[login] onLogout -> userInitiatedConnect=false");
    }

    // ---------- 调试导出 ----------
    function dump() {
        var s = {
            isLive: !!(synthWS && (synthWS.readyState === WebSocket.OPEN || synthWS.readyState === WebSocket.CONNECTING)),
            synthReady, mainPlaying: mainAudio ? !mainAudio.paused : false,
            eventPlaying: eventAudio ? !eventAudio.paused : false,
            mainLen: mainQ.length, eventLen: eventQ.length,
            metaQLen: metaQueue.length,
            inflightMain: inflightMain, inflightTotal: inflightTotal,
            autoConnectOnVisible, autoConnectIfEverConnected, everConnected, userInitiatedConnect,
        };
        console.table(s);
        console.log("lastChatMsg:", lastChatMsg || null);
        return s;
    }

    // =========================
    // ===== WX（视频号） =====
    // =========================
    var wxWS = null, wxOpen = false, wxClosing = false;
    var wxHeart = null, WX_HEARTBEAT = 25000;
    var wxOutbox = [];
    var lastWxMsg = null;

    var wxReconnectTimer = null, wxBackoff = 1000, wxBackoffMax = 30000;
    function scheduleWxReconnect(){
        if (wxClosing) return;
        if (wxReconnectTimer) return;
        wxReconnectTimer = setTimeout(function(){
            wxReconnectTimer = null;
            if (safeToken()) { log("[wx] reconnect..."); connectWx(); }
            wxBackoff = Math.min(wxBackoff * 2, wxBackoffMax);
        }, wxBackoff);
    }
    function startWxHB(){ stopWxHB(); wxHeart = setInterval(function(){ if (wxWS && wxWS.readyState === WebSocket.OPEN) { try { wxWS.send(JSON.stringify({ type: "ping" })); } catch (e) {} } }, WX_HEARTBEAT); }
    function stopWxHB(){ if (wxHeart) { clearInterval(wxHeart); wxHeart = null; } }

    function setWxHandlers(h) {
        if (!h || typeof h !== "object") return;
        wxHandlers.onQr         = h.onQr         || wxHandlers.onQr;
        wxHandlers.onQrStatus   = h.onQrStatus   || wxHandlers.onQrStatus;
        wxHandlers.onLoginOk    = h.onLoginOk    || wxHandlers.onLoginOk;
        wxHandlers.onRoomStatus = h.onRoomStatus || wxHandlers.onRoomStatus;
        wxHandlers.onStatus     = h.onStatus     || wxHandlers.onStatus;
        wxHandlers.onRaw        = h.onRaw        || wxHandlers.onRaw;
        log("[wx] handlers set");
    }

    function connectWx() {
        var token = safeToken(); if (!token) { log("[wx] wait login"); return; }
        if (wxWS && (wxWS.readyState === WebSocket.OPEN || wxWS.readyState === WebSocket.CONNECTING)) return;

        wxClosing = false;
        log("[wx] connecting:", WX_URL);
        wxWS = new WebSocket(WX_URL + "?token=" + encodeURIComponent(token));

        wxWS.onopen = function () {
            wxOpen = true;
            everConnected = true;
            startWxHB();
            wxBackoff = 1000;
            flushWxOutbox();
            log("[wx] open");
        };
        wxWS.onclose = function (ev) {
            wxOpen = false;
            stopWxHB();
            wxWS = null;
            warn("[wx] closed:", ev && ev.code, ev && ev.reason);
            if (!wxClosing) scheduleWxReconnect();
        };
        wxWS.onerror = function (e) {
            warn("[wx] error:", e && (e.message || e));
            if (!wxClosing) scheduleWxReconnect();
        };
        wxWS.onmessage = function (ev) {
            var msg; try { msg = JSON.parse(ev.data); } catch (_) { return; }
            lastWxMsg = msg;
            if (msg.type !== "ping") log("[wx] <-", msg);
            try { wxHandlers.onRaw(msg); } catch (e) {}

            if (msg.type === "wx_qr" && msg.data) {
                try { wxHandlers.onQr(msg.data); } catch (e) {}
                return;
            }
            if (msg.type === "wx_qr_status" && msg.data) {
                var s = msg.data.qr_status;
                try { wxHandlers.onQrStatus(s, msg.data); } catch (e) {}
                return;
            }
            if (msg.type === "wx_login_ok" && msg.data) {
                try { wxHandlers.onLoginOk(msg.data); } catch (e) {}
                return;
            }
            if (msg.type === "wx_room_status" && msg.data) {
                var ls = msg.data.live_status;
                try { wxHandlers.onRoomStatus(ls, msg.data); } catch (e) {}
                return;
            }
            if (msg.type === "wx_status" && msg.data) {
                var st = msg.data.state;
                try { wxHandlers.onStatus(st); } catch (e) {}
                return;
            }
            if (msg.type === "danmu_event" && msg.data) {
                // 与抖音事件对齐：统一走 onDanmuEvent
                try { onDanmuEvent(msg.data); } catch (e) { error("[wx danmu] handler error:", e); }
                return;
            }
        };
    }
    function disconnectWx() {
        wxClosing = true;
        stopWxHB();
        if (wxWS) { try { wxWS.close(1000, "normal"); } catch (e) {} }
        wxWS = null; wxOpen = false;
        log("[wx] disconnect() called");
    }
    function sendWx(obj) {
        if (wxWS && wxWS.readyState === WebSocket.OPEN) { try { wxWS.send(JSON.stringify(obj)); return true; } catch (e) { return false; } }
        wxOutbox.push(obj); connectWx(); return true;
    }
    function flushWxOutbox() { if (!wxWS || wxWS.readyState !== WebSocket.OPEN) return; var arr = wxOutbox.slice(); wxOutbox.length = 0; arr.forEach(function (o) { try { wxWS.send(JSON.stringify(o)); } catch (e) {} }); }

    // ★ 外部触发：开始扫码（请求二维码）
    function wxStart() { connectWx(); return sendWx({ type: "wx_start" }); }
    function wxStop()  { return sendWx({ type: "wx_stop"  }); }

    // ---------- 导出 ----------
    return {
        init: function (opts) {
            opts = opts || {};
            getConfig = opts.getConfig || getConfig;
            getToken  = opts.getToken  || getToken;
            onConfigUpdate = opts.onConfigUpdate || onConfigUpdate;
            onLog = opts.onLog || onLog;
            DEBUG = (opts.debug !== false);

            autoConnectOnVisible = !!opts.autoConnectOnVisible; // 默认 false
            autoConnectIfEverConnected = (opts.autoConnectIfEverConnected !== false); // 默认 true

            // ★ 新增：可在 init 注入 wx 回调（也可后续 setWxHandlers）
            if (opts.wx && typeof opts.wx === "object") setWxHandlers(opts.wx);

            ensureAudios();
            setLiveFlag(false);

            var closeWS = function () { disconnectSynth(); disconnectChat(); disconnectWx(); };
            window.addEventListener("pagehide", closeWS, { capture: true });
            window.addEventListener("beforeunload", closeWS, { capture: true });

            document.addEventListener("visibilitychange", function () {
                if (document.visibilityState === "visible") {
                    if (autoConnectOnVisible) {
                        var allowAuto = userInitiatedConnect || (autoConnectIfEverConnected && everConnected);
                        log("[vis] visible allowAuto=", allowAuto, "flags:", { userInitiatedConnect, everConnected });
                        if (allowAuto) ensureConnected(); // 仍只连 synth/chat；wx 需要用户点击 wxStart
                    }
                    if (synthReady && mainAudio && mainAudio.paused && !eventQ.length && mainQ.length) {
                        schedulePlayNextMain();
                    }
                    refreshLiveFlag();
                }
            });

            log("[init] done. opts:", { autoConnectOnVisible, autoConnectIfEverConnected, DEBUG });
        },

        // ★ 新增：登录（视频号）
        onLoginWx,

        // 登录/登出钩子
        onLogin,
        onLogout,
        ensureConnected,

        // 手势解锁 & 主队列
        unlockAudio,
        fillMain,

        // 手动插话（事件）
        addEvent: function (text, qtype) { if (!text) return; log("[event] addEvent:", qtype||"interrupt", text); synth(String(text), qtype || "interrupt"); },

        // 连接相关（抖音 + 音频）
        connectSynth, disconnectSynth,
        connectChat,  disconnectChat,
        sendDanmu, sendGetUserInfo, sendGetUserConfig, sendSetUserConfig,

        // ★ 新增：视频号（wx）
        connectWx, disconnectWx, wxStart, wxStop, setWxHandlers,

        // 主动重新下发当前 store 的 TTS 配置（例如用户切换了音色文件）
        applyTTSConfig: function (extraForm) { return sendConfigFromStore(extraForm); },
        forceRefreshConfig,

        // 方案A：控制开关与手动连接入口
        connectManually: function () { userInitiatedConnect = true; log("[manual] connect"); ensureConnected(); },
        setAutoConnectOnVisible: function (v) { autoConnectOnVisible = !!v; log("[cfg] autoConnectOnVisible=", autoConnectOnVisible); },
        setAutoConnectIfEverConnected: function (v) { autoConnectIfEverConnected = !!v; log("[cfg] autoConnectIfEverConnected=", autoConnectIfEverConnected); },

        // 调试
        debug: { dump },

        // 状态（调试）
        state: {
            get mainList()     { return mainQ; },
            get eventList()    { return eventQ; },
            get mainPlaying()  { return mainAudio ? !mainAudio.paused : false; },
            get eventPlaying() { return eventAudio ? !eventAudio.paused : false; },
            get lastChatMsg()  { return lastChatMsg; },
            get synthReady()   { return synthReady; },
            get isLive()       { return !!(synthWS && (synthWS.readyState === WebSocket.OPEN || synthWS.readyState === WebSocket.CONNECTING)); },

            get autoConnectOnVisible() { return autoConnectOnVisible; },
            get autoConnectIfEverConnected() { return autoConnectIfEverConnected; },
            get everConnected() { return everConnected; },
            get userInitiatedConnect() { return userInitiatedConnect; },

            // ★ 新增：wx 状态
            get isWxOpen()     { return !!(wxWS && wxWS.readyState === WebSocket.OPEN); },
            get lastWxMsg()    { return lastWxMsg; },
        }
    };
})();

export default Orchestrator;